package com.ctgu.chatapp.Netty

import com.ctgu.chatapp.Netty.Handler.ChatResponseHandler
import com.ctgu.chatapp.Netty.Handler.ExponentialBackOffRetry
import com.ctgu.chatapp.Netty.Handler.Ping
import com.ctgu.chatapp.Netty.Handler.ReconnectHandler
import com.ctgu.chatapp.Netty.protocol.Message
import io.netty.bootstrap.Bootstrap
import io.netty.channel.Channel
import io.netty.channel.ChannelFutureListener
import io.netty.channel.ChannelInitializer
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioSocketChannel
import io.netty.handler.codec.protobuf.ProtobufDecoder
import io.netty.handler.codec.protobuf.ProtobufEncoder
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender


class NettyClient {

    val retryPolicy = ExponentialBackOffRetry(1000, 5, 5 * 1000)

    var channel: Channel? = null

    val reconnectHandler = ReconnectHandler(this)

    private var bootStrap: Bootstrap = Bootstrap()
        .group(NioEventLoopGroup())
        .channel(NioSocketChannel::class.java)
        .handler(object : ChannelInitializer<SocketChannel>() {
            override fun initChannel(ch: SocketChannel) {
                val pipeline = ch.pipeline()
                pipeline.addLast(ProtobufVarint32LengthFieldPrepender())
                pipeline.addLast(ProtobufEncoder())
                pipeline.addLast(ProtobufVarint32FrameDecoder())
                pipeline.addLast(ProtobufDecoder(Message.IMSMsg.getDefaultInstance()))
                pipeline.addLast(Ping())
                pipeline.addLast(reconnectHandler)
                pipeline.addLast(ChatResponseHandler())
            }
        })

    companion object {
        val instance: NettyClient by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            NettyClient()
        }
    }

    private fun getConnectionListener(): ChannelFutureListener {
        return ChannelFutureListener { future ->
            if (!future.isSuccess) {
                future.channel().pipeline().fireChannelInactive()
            }
        }
    }

    fun connect() {
        synchronized(instance) {
            val future = bootStrap.connect("172.16.0.158", 8088).sync()
            future.addListener(getConnectionListener())
            this.channel = future.channel()
        }
    }

    fun disConnect() {
        synchronized(instance) {
            channel!!.disconnect()
        }
    }

    fun sendMessage(message: Any) {
        channel!!.writeAndFlush(message)
    }
}